package com.ats.audiotoscore;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;

import android.graphics.*;
import android.util.AttributeSet;
import android.content.Context;
import android.view.View;
//import android.util.Log;
import com.leff.midi.MidiFile;
import com.leff.midi.MidiTrack;
import com.leff.midi.event.NoteOff;
import com.leff.midi.event.NoteOn;
import com.leff.midi.event.meta.Tempo;
import com.leff.midi.event.meta.TimeSignature;

class MusicSheetView extends View {
//	private static final String TAG = "AudioToScore";
	private int notes[] = null;
	private int noteCount = 0;
	private int notesMax = 1024;
	private Paint paint = null;
	private Bitmap gclef = null;
	private Bitmap fclef = null;
	private Bitmap note = null;
	private Bitmap sharp = null;
//	private Bitmap flat = null;
//	private Bitmap natural = null;
	private Bitmap key = null;
	private int sheetW = 0;
	private int sheetH = 0;
	private int noteSpacing = 7;
	private int noteToY[];
	private int lineNotes[];
	private MidiTrack tempoTrack;
	private MidiTrack noteTrack;
	private int sheetLines[] = { 77, 74, 71, 67, 64, 57, 53, 50, 47, 43 };
	private int seekTo = -1;

	public MusicSheetView(Context context, AttributeSet attrs) {
		super(context, attrs);
		notes = new int[notesMax];
		gclef = BitmapFactory.decodeResource(getResources(), R.drawable.gclef);
		fclef = BitmapFactory.decodeResource(getResources(), R.drawable.fclef);
		note = BitmapFactory.decodeResource(getResources(), R.drawable.note);
		sharp = BitmapFactory.decodeResource(getResources(), R.drawable.sharp);
//		flat = BitmapFactory.decodeResource(getResources(), R.drawable.flat);
//		natural = BitmapFactory.decodeResource(getResources(), R.drawable.natural);
		key = BitmapFactory.decodeResource(getResources(), R.drawable.key);
		paint = new Paint();

		noteSpacing = key.getHeight() / 20;
		BuildNoteToY();
		MidiInit();
	}

	public void Reset() {
		noteCount = 0;
	}

	private void MidiInit() {
		tempoTrack = new MidiTrack();
		noteTrack = new MidiTrack();

        TimeSignature ts = new TimeSignature();
        ts.setTimeSignature(4, 4, TimeSignature.DEFAULT_METER, TimeSignature.DEFAULT_DIVISION);

        Tempo t = new Tempo();
        t.setBpm(100);
 
        tempoTrack.insertEvent(ts);
        tempoTrack.insertEvent(t);
	}

	private void MidiInsertNote(int note, long mstime, long duration) {
        int channel = 0, velocity = 100;

        NoteOn on = new NoteOn(mstime, channel, note, velocity);
        NoteOff off = new NoteOff(mstime + duration, channel, note, 0);

        noteTrack.insertEvent(on);
        noteTrack.insertEvent(off);
	}

	public void MidiWriteFile(String filename) {
        ArrayList<MidiTrack> tracks = new ArrayList<MidiTrack>();
        tracks.add(tempoTrack);
        tracks.add(noteTrack);

        MidiFile midi = new MidiFile(MidiFile.DEFAULT_RESOLUTION, tracks);
        File output = new File(filename);
        try
        {
            midi.writeToFile(output);
        }
        catch(IOException e)
        {
            System.err.println(e);
        }
	}

	private void BuildNoteToY() {
		int count = -1;
		int ln = 0;
		int lnc = 0;

		noteToY = new int[10*12];
		lineNotes = new int[10*5];

		for (int i=0; i<10; i++) {
			for (int n=0; n<12; n++) {
				int midiNote = i*12+n;
				boolean isNaturalNote = IsNaturalNote(midiNote);
				if (isNaturalNote) count++;
				noteToY[midiNote] = count;

				if (isNaturalNote) {
					lnc++;
					if (lnc == 2) {
						lnc = 0;
						lineNotes[ln++] = midiNote;
					}
				}

			}
		}
	}

	public void AddNote(int note, long mstime, long duration) {
		if (note >= noteToY.length)
			return;

		MidiInsertNote(note, mstime, duration);

		if (noteCount >= notesMax) {
			Reset();
		}

		notes[noteCount++] = note;
	}

	private boolean IsNaturalNote(int note) {
		switch (note % 12) {
		case 0:
		case 2:
		case 4:
		case 5:
		case 7:
		case 9:
		case 11:
			return true;
		}

		return false;
	}

	private int GetYForNote(int note) {
		if (sheetH == 0)
			return 0;
		int y = sheetH * 50 / 100;
		y = y + (noteToY[60] - noteToY[note]) * noteSpacing;
		return y;
	}

	public void UpdateSheet() {
		invalidate();
		requestLayout();
	}

	public void SeekTo(int progress) {
		seekTo = progress;
		UpdateSheet();
	}

	public int GetNoteCount() {
		return noteCount;
	}

	public int GetStartX() {
		int x = 1;

		x+= key.getWidth()+2;
		x+= 4;
		x+= gclef.getWidth()+20;
		return x;
	}

	public int GetLimitIndex() {
		if (noteCount == 0) {
			return 0;
		} else {	
			int limitI = noteCount-1;
			int xx = sheetW-5-GetMaxNoteWidth();
			int x = GetStartX();
			for (; limitI>0; limitI--) {
				xx-= GetMaxNoteWidth();
				if (xx <= x)
					break;
			}

			return limitI;
		}
	}

	private int GetMaxNoteWidth() {
		return note.getWidth()+5+sharp.getWidth()+2;
	}

	private boolean DrawNote(int midiNote, int x, Canvas canvas, Paint paint) {
		int y = GetYForNote(midiNote)+2;
		boolean isNaturalNote = IsNaturalNote(midiNote);
		boolean drawLine = true;

		if (x >= (sheetW-GetMaxNoteWidth())) {
			return false;
		}

		// Does it needs extra lines on the sheet?
		for (int j=0; j<sheetLines.length; j++) {
			if (sheetLines[j] == midiNote) {
				drawLine = false;
				break;
			}
		}

		// We gotta draw extra lines
		if (drawLine) {
			int ln = 0;
			int w = note.getWidth()+5+2;
			if (isNaturalNote == false)
				w+= sharp.getWidth()+2;

			for (int j=0; j<lineNotes.length; j++) {
				if (lineNotes[j] >= midiNote) {
					ln = j;
					break;
				}
			}

			paint.setStrokeWidth(2);
			if (lineNotes[ln] > 77) {
				// Extra lines above our staff's
				for (int j=ln; ; j--) {
					if (lineNotes[j] == 77) break;
					int yy = GetYForNote(lineNotes[j]);
					canvas.drawLine(x-2, yy, x+w, yy, paint);
				}
			} else if (lineNotes[ln] == 60) {
				// Middle C extra line
				canvas.drawLine(x-2, y, x+w, y, paint);
			} else if (lineNotes[ln] < 43) {
				// Extra lines below our staff's
				for (int j=ln; ; j++) {
					if (lineNotes[j] == 43) break;
					int yy = GetYForNote(lineNotes[j]);
					canvas.drawLine(x-2, yy, x+w, yy, paint);
				}
			}

			paint.setStrokeWidth(1);
		}

		// Draw sharp if needed
		if (isNaturalNote == false) {
			canvas.drawBitmap(sharp, x, y - sharp.getHeight() / 2, null);
			x+= sharp.getWidth()+2;
		}

		// Finally draw the note itself
		canvas.drawBitmap(note, x, y - note.getHeight() * 90 / 100, null);
		x+= note.getWidth()+5;

		return true;
	}

	@Override
	protected void onDraw(Canvas canvas) {
		super.onDraw(canvas);

		sheetW = this.getWidth();
		sheetH = this.getHeight();

		canvas.drawColor(Color.WHITE);

		paint.setStyle(Paint.Style.FILL);
		paint.setColor(Color.BLACK);
		paint.setAlpha(255);

		int x = 1;

		canvas.drawBitmap(key, x, GetYForNote(77)-3, null);
		x+= key.getWidth()+2;

		paint.setStrokeWidth(4);
		canvas.drawLine(x, GetYForNote(77), x, GetYForNote(43), paint);
		canvas.drawLine(sheetW - 10, GetYForNote(77), sheetW - 10, GetYForNote(43), paint);
		x+= 4;
		paint.setStrokeWidth(2);
		for (int i=0; i<sheetLines.length; i++) {
			int y = GetYForNote(sheetLines[i]);
			canvas.drawLine(key.getWidth()+1, y, sheetW - 10, y, paint);
		}
		paint.setStrokeWidth(1);

		canvas.drawBitmap(gclef, x, GetYForNote(67)-gclef.getHeight()*64/100, null);
		canvas.drawBitmap(fclef, x, GetYForNote(53)-fclef.getHeight()*40/100, null);
		x+= gclef.getWidth()+20;

		if (noteCount == 0)
			return;

		int limitI = GetLimitIndex();
		int i;
		if (seekTo == -1 || seekTo >= limitI) {
			i = limitI;
		} else {
			i = seekTo;
		}

		for (; i<noteCount && x < (sheetW - 10); i++) {
			DrawNote(notes[i], x, canvas, paint);
			x+= GetMaxNoteWidth();
		}
	}
}
